//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "kminner.h"
#include <kmemdet.h>

#if defined(KERNEL_MEMORY_DETECT)

#define KMEM_DETECT_INVALID_DEPTH 2

#define KMEM_HASH_INDEX(TYPE, KMEMADDRESS) \
        (TYPE)(((TYPE)KMEMADDRESS >> 4) % KMEM_DETECT_HASH_SIZE)

// global data or functions declare
PKMemBlock g_kmemHashList[KMEM_DETECT_HASH_SIZE];
UInt32 g_kmemCurBlockCount = 0;
UInt32 g_kmemCurAllocedBlockSize = 0;
UInt32 g_kmemCurPageAllotTimes = 0;
UInt32 g_kmemCurAllocedPageCount = 0; //current alloced total page count

Void KMemInsertIntoHash(const PVoid pv, const UInt32 uSizeOrOrder,
    Boolean bPhysPage);
Boolean KMemDetachFromHash(PVoid pv, Boolean bPhysPage);

//EXTERN
EXTERN_C uint_t * __cdecl kmalloc(uint_t);
EXTERN_C void __cdecl kfree(uint_t *);
EXTERN Thread *GetThread0();

//local data or functions declare
static Void KMemDumpCallStack(Address *callStack);
CMemoryAllocatorLock g_kmemLock;

Void KMemDetInit();

Void KMemDetInit()
{
    memset(g_kmemHashList, NULL, KMEM_DETECT_HASH_SIZE * sizeof(PKMemBlock));
}

Void KMemInsertIntoHash(const PVoid pv, const UInt32 uSizeOrOrder,
    Boolean bPhysPage)
{
    if (NULL == pv) return;

    // new one KMemBlock
    PKMemBlock pb = \
        (PKMemBlock)kmalloc(
            SIZE_TO_INDEX(sizeof(KMemBlock) + sizeof(UInt32)));
    ASSERT_MEM_NULL(pb);

    g_kmemLock.Lock();

    // fill every KMemBlock field
    if (!bPhysPage) {
        pb->pAddr = pv;
        pb->uSize = uSizeOrOrder;
        g_kmemCurBlockCount++;
        g_kmemCurAllocedBlockSize += pb->uSize;
    }
    else {
        pb->pAddr = VIRTADDR(pv);
        pb->uSize = 1 << uSizeOrOrder; // page count
        g_kmemCurPageAllotTimes++;
        g_kmemCurAllocedPageCount += pb->uSize;
    }
    pb->bPhysPage = bPhysPage;
    pb->pThread = GetCurrentThread();
    if (pb->pThread != GetThread0()) {
        KMemDumpCallStack(pb->callStack);
    }
    pb->Initialize();

    // insert KMemBlock into hash
    UInt32 uIndex = KMEM_HASH_INDEX(UInt32, pb->pAddr);

    if (NULL == g_kmemHashList[uIndex]) {
        g_kmemHashList[uIndex] = pb;
    }
    else {
        g_kmemHashList[uIndex]->InsertLast(pb);
    }

    g_kmemLock.Unlock();
}

Boolean KMemDetachFromHash(PVoid pv, Boolean bPhysPage)
{
    if (NULL == pv) return TRUE;

    g_kmemLock.Lock();

    PVoid pvdetach = pv;
    if (bPhysPage) pvdetach = VIRTADDR(pv);

    UInt32 uIndex = KMEM_HASH_INDEX(UInt32, pvdetach);

    PKMemBlock pb = g_kmemHashList[uIndex];

    if (pb) {
        //the first of someone list
        if (pb->pAddr == pvdetach) {
            if (pb == pb->Next()) {// only one node in the list
                g_kmemHashList[uIndex] = NULL;
                if (!bPhysPage) {
                    g_kmemCurAllocedBlockSize -= pb->uSize;
                    g_kmemCurBlockCount--;
                }
                else {
                    g_kmemCurAllocedPageCount -= pb->uSize;
                    g_kmemCurPageAllotTimes--;
                }
                kfree((uint_t *)(void *)pb);
            }
            else {
                g_kmemHashList[uIndex] = (PKMemBlock)pb->Next();
                pb->Detach();
                if (!bPhysPage) {
                    g_kmemCurAllocedBlockSize -= pb->uSize;
                    g_kmemCurBlockCount--;
                }
                else {
                    g_kmemCurAllocedPageCount -= pb->uSize;
                    g_kmemCurPageAllotTimes--;
                }
                kfree((uint_t *)(void *)pb);
            }

            g_kmemLock.Unlock();
            return TRUE;
        }

        // another node in someone list
        ForEachDLinkNode(PKMemBlock, pb, g_kmemHashList[uIndex]) {
            if (pb->pAddr == pvdetach) {
                pb->Detach();
                if (!bPhysPage) {
                    g_kmemCurAllocedBlockSize -= pb->uSize;
                    g_kmemCurBlockCount--;
                }
                else {
                    g_kmemCurAllocedPageCount -= pb->uSize;
                    g_kmemCurPageAllotTimes--;
                }
                kfree((uint_t *)(void *)pb);
                g_kmemLock.Unlock();
                return TRUE;
            }
        }
    }

    g_kmemLock.Unlock();
    return FALSE;
}

static Void  KMemDumpCallStack(Address *callStack)
{
#if defined(_GNUC)

#if defined(_x86)
    __asm__ __volatile__(
        "pushl      %%esi;"
        "pushl      %%edi;"
        "pushl      %%ecx;"
        "pushl      %%eax;"
        "movl       %%ebp, %%esi;"
        "movl       %0, %%edi;"
        "movl       %1, %%ecx;"
        "inc        %%ecx;"
        :
        :"g"(callStack), "g"(KMEM_DETECT_INVALID_DEPTH)
    );

    //// ignore MEMSPY_STACK_INVALID_DEGREE calls
    __asm__ __volatile__(
    "Ignore:    or          %esi, %esi;"
                "jz         SetAllZero;"
                "movl       (%esi), %esi;"
                "loop       Ignore"
    );

    //// dump MEMSPY_STACK_RECORD_DEPTH call stack pointers
    __asm__ __volatile__(
            "movl       %0, %%ecx;"
    "Next:   or         %%esi, %%esi;"
            "jz         SetZero;"
            "movl       4(%%esi), %%eax;"
            "movl       (%%esi), %%esi;"
            "stosl;"
            "loop       Next;"
            "jmp        SetZero;"
    "SetAllZero:  mov   %0, %%ecx;"
             :
             :"g"(KMEM_DETECT_STACK_DEPTH)
    );

    //// dump 0 into the left fields of callStack
    __asm__ __volatile__(
    "SetZero:   xor        %eax, %eax;"
                "rep        stosl;"
                "popl       %eax;"
                "popl       %ecx;"
                "popl       %edi;"
                "popl       %esi"
    );
#elif defined(_arm)
//
//    Stack Backtrace Structure
//    =========================
//
//    +---------------------+
//    |  save code pointer  |  [fp]       fp points here
//    +---------------------+
//    |  return link value  |  [fp, #-4]
//    +---------------------+
//    |  return sp value    |  [fp, #-8]
//    +---------------------+
//    |  return fp value    |  [fp, #-12] points to next structure
//    +---------------------+

    UInt32 fp;

    __asm__ __volatile__(
        "str  fp, %0;"
        :"=m"(fp));

    for (Int32 i = 0; i < KMEM_DETECT_INVALID_DEPTH; i++) {
        if (!fp) {
            callStack[i] = 0;
        }
        else {
            callStack[i] = *(PUInt32)((PUInt32)fp - 1);
            fp = *(PUInt32)((PUInt32)fp - 3);
        }
    }
#endif
#elif defined(_EVC)

// BUGBUG:
// TODO:

#else

#error Unknown C++ compiler

#endif // defined(_GNUC)
}

#endif //KERNEL_MEMORY_DETECT
